﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using UnityEngine;

namespace EvaOxygen
{
    //launches the behavior which runs all the logic
    //seems very odd to derive from unitTest, however lots of mods currently doing so...
    public class EvaOxygen : KSP.Testing.UnitTest
    {
        public EvaOxygen()
            : base()
        {
            if (EvaBehaviour.GameObjectInstance == null)
                EvaBehaviour.GameObjectInstance = GameObject.Find("EvaBehaviour") ?? new GameObject("EvaBehaviour", typeof(EvaBehaviour));
        }
    }

    public class EvaBehaviour : MonoBehaviour
    {
        //ourself
        public static GameObject GameObjectInstance;

        //last vessel that was active
        public Vessel LastVessel = null;

        //just revived unit
        public EvaPartModule RevivedUnit = null;

        //active unit (set to null if active unit is not EvaPartModule)
        public EvaPartModule ActiveUnit = null;

        //List of all EvaPartModules
        public Hashtable Units = null;

        //invalid timer value
        public const int InvalidTimer = -100;

        public void Awake()
        {
            //dont destroy this thing
            DontDestroyOnLoad(this);
        }

        public void SetAlarm(EvaPartModule evaPart, int timer)
        {
            Units[evaPart] = timer;
        }

        public void AllowRevive()
        {
            foreach (EvaPartModule eva in Units.Keys)
            {
                if (eva && eva.ReviveActive)
                {
                    eva.Events["Revive"].active = true;
                }
            }
        }

        public void DisallowRevive()
        {
            foreach (EvaPartModule eva in Units.Keys)
            {
                if (eva && eva != ActiveUnit)
                {
                    eva.Events["Revive"].active = false;
                }
            }
        }

        public void LateUpdate()
        {
            if (ActiveUnit)
            {
                //prevent revive from happening if we are knocked out
                if (ActiveUnit.ReviveActive)
                {
                    DisallowRevive();
                }
                else
                {
                    AllowRevive();
                }

                //if revive just happened then even out oxygen between the 2 kerbals
                if (RevivedUnit)
                {
                    double oxygen = ActiveUnit.HalfOxygen();
                    RevivedUnit.SetOxygen(oxygen);

                    Units[ActiveUnit] = ActiveUnit.GetAlarmValue();
                    Units[RevivedUnit] = RevivedUnit.GetAlarmValue();

                    //get rid of revivedUnit
                    RevivedUnit = null;
                }
            }
        }

        public void Update()
        {
            //if game is loaded and you are flying and active vessel exists
            if (FlightGlobals.fetch != null && FlightGlobals.ActiveVessel != null && HighLogic.LoadedScene == GameScenes.FLIGHT)
            {
                //if units list is not setup
                if (Units == null)
                {
                    Units = new Hashtable();

                    foreach (Vessel vessel in FlightGlobals.Vessels)
                    {
                        foreach (Part part in vessel.Parts)
                        {
                            if (part)
                            {
                                foreach (PartModule module in part.Modules)
                                {
                                    if (module)
                                    {
                                        if (module.GetType() == typeof(EvaPartModule))
                                        {
                                            Units.Add((EvaPartModule)module, ((EvaPartModule)module).GetAlarmValue());
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                //if active vessel has changed
                if (FlightGlobals.ActiveVessel != LastVessel)
                {
                    print("EvaOxygen : New active vessel");
                    LastVessel = FlightGlobals.ActiveVessel;

                    if (ActiveUnit != null)
                    {
                        AllowRevive();
                    }
                    ActiveUnit = null;

                    //check if vessel has KerbalEva PartModule
                    PartModule kerbalEva = null;
                    foreach (Part part in LastVessel.Parts)
                    {
                        if (part)
                        {
                            foreach (PartModule module in part.Modules)
                            {
                                if (module)
                                {
                                    if (module.GetType() == typeof(KerbalEVA))
                                    {
                                        kerbalEva = module;
                                    }
                                    else if (module.GetType() == typeof(EvaPartModule))
                                    {
                                        ActiveUnit = (EvaPartModule)module;

                                        if (Units.ContainsKey(ActiveUnit) == false)
                                        {
                                            Units.Add(ActiveUnit, InvalidTimer);
                                        }
                                    }
                                }
                            }
                        }
                    }

                    if (kerbalEva)
                    {
                        print("EvaOxygen : Its a KerbalEVA!");

                        //Instantiate the controller for the active vessel.
                        //This uses weak references to ensure partModule only gets added once
                        if (EvaController.GetInstance(kerbalEva))
                        {
                            print("EvaOxygen : Its a new KerbalEVA");

                            //dynamically add a partModule to keep track of oxygen
                            ConfigNode addNode = new ConfigNode("Module");
                            addNode.AddValue("name", "EvaPartModule");

                            PartModule module = kerbalEva.part.AddModule(addNode);

                            // uses reflection to find and call the PartModule.Awake() private method
                            if (Awaken(module))
                            {
                                ((EvaPartModule)module).ourManager = this;
                                ActiveUnit = ((EvaPartModule)module);
                            }
                        }
                        else
                        {
                            print("EvaOxygen : Old KerbalEVA don't reset time");
                        }
                    }
                }

                //check timers
                //List<EvaPartModule> keys = new List<EvaPartModule>();
                EvaPartModule[] keys = new EvaPartModule[Units.Keys.Count];
                Units.Keys.CopyTo(keys, 0);
                //foreach (DictionaryEntry de in Units)
                //{
                //    keys.Add((EvaPartModule)de.Key);
                //}

                foreach (EvaPartModule eva in keys)
                {
                    if (eva)
                    {
                        //remove dead units
                        if (eva.vessel.state == Vessel.State.DEAD)
                        {
                            Units.Remove(eva);
                            continue;
                        }

                        int timer = (int)Units[eva];
                        if (timer == InvalidTimer)
                            continue;

                        int updatedTimer = eva.GetAlarmValue();
                        if (updatedTimer != InvalidTimer && updatedTimer <= 0)
                        {
                            if (TimeWarp.CurrentRateIndex != 0)
                            {
                                TimeWarp.SetRate(0, true);
                            }

                            string name = eva.kerbalEva.name;
                            int start = name.IndexOf('(') + 1;
                            int end = name.IndexOf(')');
                            string toPrint = name.Substring(start, end - start) + " is running low on oxygen!!!   ";

                            if (eva.vessel.situation == Vessel.Situations.LANDED ||
                                eva.vessel.situation == Vessel.Situations.PRELAUNCH ||
                                eva.vessel.situation == Vessel.Situations.SPLASHED)
                            {
                                toPrint += "On ground of " + eva.vessel.mainBody.bodyName;
                            }
                            else
                            {
                                toPrint += eva.vessel.heightFromSurface + " distance from " + eva.vessel.mainBody.bodyName;
                            }

                            ScreenMessages.PostScreenMessage(toPrint, 8, ScreenMessageStyle.UPPER_CENTER);


                            updatedTimer = InvalidTimer;
                        }

                        Units[eva] = updatedTimer;
                    }
                }
            }
        }

        public static bool Awaken(PartModule module)
        {
            // thanks to Mu and Kine (and ialdabaoth for posting answer on forum) for help with this code
            if (module == null)
            {
                print("EvaOxygen : EvaController no module");
                return false;
            }

            object[] paramList = new object[] { };
            MethodInfo awakeMethod = typeof(PartModule).GetMethod("Awake", BindingFlags.Instance | BindingFlags.NonPublic);

            if (awakeMethod == null)
            {
                print("EvaOxygen : EvaController no awake method");
                return false;
            }

            awakeMethod.Invoke(module, paramList);
            return true;
        }
    }
}
